module rm {
	/**
	 * Create by richliu1023
	 * @date 2016-06-06
	 * @email richliu1023@gmail.com
	 * @github https://github.com/RichLiu1023
	 * @description a simple explanation
	 */
	export class HoverController extends rm.ControllerBase {
		private _currentPanAngle:number = 0;
		private _currentTiltAngle:number = 90;

		private _panAngle:number = 0;
		private _tiltAngle:number = 90;
		private _distance:number = 1000;
		private _minPanAngle:number = -Infinity;
		private _maxPanAngle:number = Infinity;
		private _minTiltAngle:number = -90;
		private _maxTiltAngle:number = 90;
		private _maxDistance:number = 1000;
		private _minDistance:number = -1000;
		private _steps:number = 8;
		private _yFactor:number = 2;
		private _wrapPanAngle:boolean = false;
		private _lookAtPosition:egret3d.Vector3D = new egret3d.Vector3D( 0.0, 0.0, 0.0 );
		private _mouseDown:boolean = false;
		private _mouseRightDown:boolean = false;
		private _keyArray:Array<boolean> = new Array<boolean>();

		private _isOpenFree:boolean = false;

		/**
		 * @param targetObject 控制的目标相机，目标对象
		 * @param lookAtObject 被观察对象
		 * @param panAngle 相机旋转时，当前Y轴的旋转角度
		 * @param tiltAngle 相机旋转时，当前X轴的旋转角度
		 * @param distance 被观察目标与相机之间的距离
		 * @param minTiltAngle 相机最小的X轴旋转角度
		 * @param maxTiltAngle 相机旋转时最大的X轴旋转角度
		 * @param minPanAngle 相机旋转时最小的Y轴旋转角度
		 * @param maxPanAngle 相机旋转时最大的Y轴旋转角度
		 * @param steps 移动结束时，平滑步数。数值越大，最后缓动越加缓慢
		 * @param yFactor 旋转时Y轴的一个相对变化值
		 * @param wrapPanAngle 是否开启PanAngle角度限制
		 */
		constructor( targetObject:egret3d.Object3D = null, lookAtObject:egret3d.Object3D = null,
					 panAngle:number = 0, tiltAngle:number = 90, distance:number = 100,
					 minTiltAngle:number = -90, maxTiltAngle:number = 90, minPanAngle:number = NaN,
					 maxPanAngle:number = NaN, steps:number = 8, yFactor:number = 2, wrapPanAngle:boolean = false ) {
			super( targetObject, lookAtObject );

			this.distance = distance;
			this.panAngle = panAngle;
			this.tiltAngle = tiltAngle;
			this.minPanAngle = minPanAngle || -Infinity;
			this.maxPanAngle = maxPanAngle || Infinity;
			this.minTiltAngle = minTiltAngle;
			this.maxTiltAngle = maxTiltAngle;
			this.steps = steps;
			this.yFactor = yFactor;
			this.wrapPanAngle = wrapPanAngle;

			this._currentPanAngle = this._panAngle;
			this._currentTiltAngle = this._tiltAngle;
		}

		private addEvent():void {
			egret3d.Input.addEventListener( egret3d.MouseEvent3D.MOUSE_MOVE, this.mouseMove, this );
			egret3d.Input.addEventListener( egret3d.MouseEvent3D.MOUSE_WHEEL, this.mouseWheel, this );
			egret3d.Input.addEventListener( egret3d.MouseEvent3D.MOUSE_UP, this.mouseUp, this );
			egret3d.Input.addEventListener( egret3d.MouseEvent3D.MOUSE_DOWN, this.mouseDown, this );

			//egret3d.Input.addEventListener( egret3d.KeyEvent3D.KEY_UP, this.keyUp, this );
			//egret3d.Input.addEventListener( egret3d.KeyEvent3D.KEY_DOWN, this.keyDown, this );

			egret3d.Input.addEventListener( egret3d.TouchEvent3D.TOUCH_END, this.touchUp, this );
			egret3d.Input.addEventListener( egret3d.TouchEvent3D.TOUCH_START, this.touchDown, this );
			egret3d.Input.addEventListener( egret3d.TouchEvent3D.TOUCH_MOVE, this.touchMove, this );
		}

		private removeEvent():void {
			egret3d.Input.removeEventListener( egret3d.MouseEvent3D.MOUSE_MOVE, this.mouseMove, this );
			egret3d.Input.removeEventListener( egret3d.MouseEvent3D.MOUSE_WHEEL, this.mouseWheel, this );
			egret3d.Input.removeEventListener( egret3d.MouseEvent3D.MOUSE_UP, this.mouseUp, this );
			egret3d.Input.removeEventListener( egret3d.MouseEvent3D.MOUSE_DOWN, this.mouseDown, this );

			//egret3d.Input.removeEventListener( egret3d.KeyEvent3D.KEY_UP, this.keyUp, this );
			//egret3d.Input.removeEventListener( egret3d.KeyEvent3D.KEY_DOWN, this.keyDown, this );

			egret3d.Input.removeEventListener( egret3d.TouchEvent3D.TOUCH_END, this.touchUp, this );
			egret3d.Input.removeEventListener( egret3d.TouchEvent3D.TOUCH_START, this.touchDown, this );
			egret3d.Input.removeEventListener( egret3d.TouchEvent3D.TOUCH_MOVE, this.touchMove, this );
		}

		public set isOpenFree( value:boolean ) {
			if ( this._isOpenFree == value )return;
			this._isOpenFree = value;
			if ( value ) {
				this.addEvent();
			}
			else {
				this.removeEvent();
			}
		}

		public get isOpenFree():boolean {
			return this._isOpenFree;
		}

		private mouseMove( m:egret3d.MouseEvent3D ) {
			if ( this._mouseDown ) {
				this._tiltAngle += egret3d.Input.mouseOffsetY * 0.1;
				this._tiltAngle = Math.max( this._minTiltAngle, Math.min( this._maxTiltAngle, this._tiltAngle ) )

				this._panAngle += egret3d.Input.mouseOffsetX * 0.1;
				this._panAngle = Math.max( this._minPanAngle, Math.min( this._maxPanAngle, this._panAngle ) )
			}
		}

		private mouseWheel( m:egret3d.MouseEvent3D ) {
			this._distance -= egret3d.Input.wheelDelta * 0.025;
			this._distance = Math.max( this._minDistance, Math.min( this._maxDistance, this._distance ) );
		}

		private mouseUp( m:egret3d.MouseEvent3D ) {
			switch ( m.mouseCode ) {
				case egret3d.MouseCode.Mouse_Left:
					this._mouseDown = false;
					break;
				case egret3d.MouseCode.Mouse_Right:
					this._mouseRightDown = false;
					break;
			}
		}

		private mouseDown( m:egret3d.MouseEvent3D ) {
			switch ( m.mouseCode ) {
				case egret3d.MouseCode.Mouse_Left:
					this._mouseDown = true;
					break;
				case egret3d.MouseCode.Mouse_Right:
					this._mouseRightDown = true;
					break;
			}
		}

		private touchMove( t:egret3d.TouchEvent3D ) {
			if ( t.targetTouches.length == 1 ) {
				this.mouseMove( null );
			}
			else {
				this.mouseWheel( null );
			}
		}

		private touchUp( m:egret3d.TouchEvent3D ) {
			this._mouseDown = false;
		}

		private touchDown( m:egret3d.MouseEvent3D ) {
			this._mouseDown = true;
		}

		private keyDown( key:egret3d.KeyEvent3D ) {
			switch ( key.keyCode ) {
				case egret3d.KeyCode.Key_W:///w
					this._keyArray[ 0 ] = true;
					break;
				case egret3d.KeyCode.Key_A:///a
					this._keyArray[ 1 ] = true;
					break;
				case egret3d.KeyCode.Key_S:///s
					this._keyArray[ 2 ] = true;
					break;
				case egret3d.KeyCode.Key_D:///d
					this._keyArray[ 3 ] = true;
					break;
			}
		}

		private keyUp( key:egret3d.KeyEvent3D ) {
			switch ( key.keyCode ) {
				case egret3d.KeyCode.Key_W:///w
					this._keyArray[ 0 ] = false;
					break;
				case egret3d.KeyCode.Key_A:///a
					this._keyArray[ 1 ] = false;
					break;
				case egret3d.KeyCode.Key_S:///s
					this._keyArray[ 2 ] = false;
					break;
				case egret3d.KeyCode.Key_D:///d
					this._keyArray[ 3 ] = false;
					break;
			}
		}

		/**
		 * 返回目标的位置
		 * @returns 目标的位置
		 */
		public get lookAtPosition():egret3d.Vector3D {
			return this._lookAtPosition;
		}

		/**
		 * 设置目标坐标
		 * @param val 摄像机看向的目标点
		 */
		public set lookAtPosition( val:egret3d.Vector3D ) {
			this._lookAtPosition = val;
			this.notifyUpdate();
		}

		public get steps():number {
			return this._steps;
		}

		public set steps( val:number ) {
			val = (val < 1) ? 1 : val;
			if ( this._steps == val )
				return;
			this._steps = val;
			this.notifyUpdate();
		}

		/**
		 * 得到相机y轴旋转角度
		 * @returns 相机y轴旋转角度
		 */
		public get panAngle():number {
			return this._panAngle;
		}

		/**
		 * 设置相机y轴旋转
		 * @param val 旋转角度
		 */
		public set panAngle( val:number ) {
			val = Math.max( this._minPanAngle, Math.min( this._maxPanAngle, val ) );
			if ( this._panAngle == val )
				return;
			this._panAngle = val;
			this.notifyUpdate();
		}

		/**
		 * 得到相机x轴旋转角度
		 * @returns 相机x轴旋转角度
		 */
		public get tiltAngle():number {
			return this._tiltAngle;
		}

		/**
		 * 设置相机x轴旋转
		 * @param val 旋转角度
		 */
		public set tiltAngle( val:number ) {
			val = Math.max( this._minTiltAngle, Math.min( this._maxTiltAngle, val ) );
			if ( this._tiltAngle == val )
				return;
			this._tiltAngle = val;
			this.notifyUpdate();
		}

		/**
		 * 得到目标和相机的距离
		 * @returns 目标和相机的距离
		 */
		public get distance():number {
			return this._distance;
		}

		/**
		 * 设置目标和相机的距离
		 * @param val 距离
		 */
		public set distance( val:number ) {
			if ( this._distance == val )
				return;
			this._distance = this._distance = Math.max( this._minDistance, Math.min( this._maxDistance, val ) );
			this.notifyUpdate();
		}

		/**
		 * 得到相机最小y轴旋转角度
		 * @returns 相机最小x轴旋转角度
		 */
		public get minPanAngle():number {
			return this._minPanAngle;
		}

		/**
		 * 设置相机最小y轴旋转角度
		 * @param val 相机最小x轴旋转角度
		 */
		public set minPanAngle( val:number ) {
			if ( this._minPanAngle == val )
				return;
			this._minPanAngle = val;
			this.panAngle = Math.max( this._minPanAngle, Math.min( this._maxPanAngle, this._panAngle ) );
		}

		/**
		 * 得到相机最大y轴旋转角度
		 * @returns 相机最大y轴旋转角度
		 */
		public get maxPanAngle():number {
			return this._maxPanAngle;
		}

		/**
		 * 设置相机最大y轴旋转角度
		 * @param val 相机最大y轴旋转角度
		 */
		public set maxPanAngle( val:number ) {
			if ( this._maxPanAngle == val )
				return;
			this._maxPanAngle = val;
			this.panAngle = Math.max( this._minPanAngle, Math.min( this._maxPanAngle, this._panAngle ) );
		}

		/**
		 * 得到相机最小x轴旋转角度
		 * @returns 相机最小x轴旋转角度
		 */
		public get minTiltAngle():number {
			return this._minTiltAngle;
		}

		/**
		 * 设置相机最小x轴旋转角度
		 * @param val 相机最小x轴旋转角度
		 */
		public set minTiltAngle( val:number ) {
			if ( this._minTiltAngle == val )
				return;
			this._minTiltAngle = val;
			this.tiltAngle = Math.max( this._minTiltAngle, Math.min( this._maxTiltAngle, this._tiltAngle ) );
		}

		/**
		 * 得到相机最大x轴旋转角度
		 * @returns 相机最大x轴旋转角度
		 */
		public get maxTiltAngle():number {
			return this._maxTiltAngle;
		}

		/**
		 * 设置相机最大x轴旋转角度
		 * @param val 相机最大x轴旋转角度
		 */
		public  set maxTiltAngle( val:number ) {
			if ( this._maxTiltAngle == val )
				return;
			this._maxTiltAngle = val;
			this.tiltAngle = Math.max( this._minTiltAngle, Math.min( this._maxTiltAngle, this._tiltAngle ) );
		}

		/**
		 * 设置相机和目标最大的距离
		 * @param val 相机和目标最大的距离
		 */
		public set maxDistance( val:number ) {
			if ( this._maxDistance == val )
				return;
			this._maxDistance = val;
			this._distance = Math.max( this._minDistance, Math.min( this._maxDistance, this._distance ) );
		}

		/**
		 * 得到相机和目标最大的距离
		 * @returns 相机和目标最大的距离
		 */
		public get maxDistance():number {
			return this._maxDistance;
		}

		/**
		 * 设置相机和目标最小的距离
		 * @param val 相机和目标最小的距离
		 */
		public set minDistance( val:number ) {
			if ( this._minDistance == val )
				return;
			this._minDistance = val;
			this._distance = Math.max( this._minDistance, Math.min( this._maxDistance, this._distance ) );
		}

		/**
		 * 得到相机和目标最小的距离
		 * @returns 相机和目标最小的距离
		 */
		public get minDistance():number {
			return this._maxDistance;
		}

		/**
		 * 旋转时Y轴的一个相对变化值
		 */
		public  get yFactor():number {
			return this._yFactor;
		}

		/**
		 * 旋转时Y轴的一个相对变化值
		 */
		public  set yFactor( val:number ) {
			if ( this._yFactor == val )
				return;
			this._yFactor = val;
			this.notifyUpdate();
		}

		/**
		 * 是否开启PanAngle角度限制
		 */
		public  get wrapPanAngle():boolean {
			return this._wrapPanAngle;
		}

		/**
		 * 是否开启PanAngle角度限制
		 */
		public set wrapPanAngle( val:boolean ) {
			if ( this._wrapPanAngle == val )
				return;
			this._wrapPanAngle = val;
			this.notifyUpdate();
		}

		/**
		 * 控制器数据更新
		 * @param interpolate
		 */
		public update( interpolate:boolean = true ):void {
			if ( this._tiltAngle != this._currentTiltAngle || this._panAngle != this._currentPanAngle ) {
				this.notifyUpdate();
				if ( this._wrapPanAngle ) {
					if ( this._panAngle < 0 )
						this._panAngle = (this._panAngle % 360) + 360;
					else
						this._panAngle = this._panAngle % 360;

					if ( this._panAngle - this._currentPanAngle < -180 )
						this._currentPanAngle -= 360;
					else if ( this._panAngle - this._currentPanAngle > 180 )
						this._currentPanAngle += 360;
				}

				if ( interpolate ) {
					this._currentTiltAngle += (this._tiltAngle - this._currentTiltAngle) / (this.steps + 1);
					this._currentPanAngle += (this._panAngle - this._currentPanAngle) / (this.steps + 1);
				} else {
					this._currentPanAngle = this._panAngle;
					this._currentTiltAngle = this._tiltAngle;
				}

				if ( (Math.abs( this._tiltAngle - this._currentTiltAngle ) < 0.01) && (Math.abs( this._panAngle - this._currentPanAngle ) < 0.01) ) {
					this._currentTiltAngle = this._tiltAngle;
					this._currentPanAngle = this._panAngle;
				}
			}

			var pos:egret3d.Vector3D = (this._lookAtObject) ? this._lookAtObject.position : (this._lookAtPosition) ? this._lookAtPosition : this._origin;
			var p:egret3d.Vector3D = new egret3d.Vector3D();
			p.x = pos.x + this.distance * Math.sin( this._currentPanAngle * egret3d.MathUtil.DEGREES_TO_RADIANS ) * Math.cos(
					this._currentTiltAngle * egret3d.MathUtil.DEGREES_TO_RADIANS );
			p.z = pos.z + this.distance * Math.cos( this._currentPanAngle * egret3d.MathUtil.DEGREES_TO_RADIANS ) * Math.cos(
					this._currentTiltAngle * egret3d.MathUtil.DEGREES_TO_RADIANS );
			p.y = pos.y + this.distance * Math.sin( this._currentTiltAngle * egret3d.MathUtil.DEGREES_TO_RADIANS ) * this.yFactor;

			if ( this._target ) {
				if ( this._lookAtPosition )
					this._target.lookAt( p, this._lookAtPosition );
			}

		}
	}
}